/**
 * Copyright (c) 2019 - 2020 IoT Company of Midea Group.
 *
 * File Name 	    : 
 * Description	    : rtos adaptor
 *
 * Version	        : v0.1
 * Author			: 
 * Date	            : 2019/09/15  refactoring
 * History	        : 
  */

#ifndef __MS_OSAL_SOCKET_H__
#define __MS_OSAL_SOCKET_H__

#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

#ifdef WITH_LINUX
#include <netinet/in.h>
#include <sys/socket.h>
#define SIN_ZERO_LEN 8
#else
#ifdef WITH_LWIP
#include "lwip/sockets.h"
#include "lwipopts.h"
#endif
#endif

#ifdef WITH_LWIP
#define MS_FD_SETSIZE FD_SETSIZE
#define MS_AF_UNSPEC AF_UNSPEC
#define MS_AF_INET AF_INET
#ifdef LWIP_MSDK_IPV6_SUPPORT
#define MS_AF_INET6 10
#define MS_IPPROTO_IPV6 41
#define MS_IPPROTO_ICMPV6 58
#endif /* LWIP_IPV6 */
#define MS_SOCK_STREAM SOCK_STREAM
#define MS_SOCK_DGRAM SOCK_DGRAM
#define MS_SIN_ZERO_LEN SIN_ZERO_LEN
#define MS_SOL_SOCKET SOL_SOCKET
#define MS_SO_TYPE SO_TYPE
#define MS_SO_SNDTIMEO SO_SNDTIMEO
#define MS_SO_RCVTIMEO SO_RCVTIMEO
#define MS_SO_REUSEADDR SO_REUSEADDR
#define MS_SO_KEEPALIVE SO_KEEPALIVE
#define MS_SO_BROADCAST SO_BROADCAST
#else
#define MS_FD_SETSIZE 1024
#define MS_AF_UNSPEC 0
#define MS_AF_INET 2
#ifdef LWIP_MSDK_IPV6_SUPPORT
#define MS_AF_INET6 10
#define MS_IPPROTO_IPV6 41
#define MS_IPPROTO_ICMPV6 58
#endif /* LWIP_IPV6 */
#define MS_SOCK_STREAM 1
#define MS_SOCK_DGRAM 2
#define MS_SIN_ZERO_LEN 8
#define MS_SOL_SOCKET 1
#define MS_SO_TYPE 3
#define MS_SO_SNDTIMEO 0x1005  /* send timeout */
#define MS_SO_RCVTIMEO 0x1006  /* receive timeout */
#define MS_SO_REUSEADDR 0x0004 /* Allow local address reuse */
#define MS_SO_KEEPALIVE 0x0008 /* keep connections alive */
#define MS_SO_BROADCAST 0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */
#endif
#define MS_IPADDR_ANY ((uint32_t)0x00000000UL)
#define MS_INADDR_ANY MS_IPADDR_ANY
#ifdef WITH_LWIP
#define MS_IPPROTO_UDP IPPROTO_UDP
#define MS_IPPROTO_TCP IPPROTO_TCP
#define MS_TCP_NODELAY 0x01
#define MS_TCP_KEEPALIVE TCP_KEEPALIVE
#define MS_TCP_KEEPIDLE TCP_KEEPIDLE
#define MS_TCP_KEEPINTVL TCP_KEEPINTVL
#define MS_TCP_KEEPCNT TCP_KEEPCNT
#else
#define MS_IPPROTO_UDP 17
#define MS_IPPROTO_TCP 6
#define MS_TCP_NODELAY 0x01   /* don't delay send to coalesce packets */
#define MS_TCP_KEEPALIVE 0x02 /* send KEEPALIVE probes when idle for pcb->keep_idle milliseconds */
#define MS_TCP_KEEPIDLE 0x03  /* set pcb->keep_idle  - Same as TCP_KEEPALIVE, but use seconds for get/setsockopt */
#define MS_TCP_KEEPINTVL 0x04 /* set pcb->keep_intvl - Use seconds for get/setsockopt */
#define MS_TCP_KEEPCNT 0x05   /* set pcb->keep_cnt   - Use number of probes sent for get/setsockopt */
#endif
#define MS_SHUT_RD 0
#define MS_SHUT_WR 1
#define MS_SHUT_RDWR 2

/**
 * @note if workshop had lwip, define this.
 */
#ifdef WITH_LWIP
typedef sa_family_t ms_sa_family_t;
typedef socklen_t ms_socklen_t;
typedef in_addr_t ms_in_addr_t;
typedef in_port_t ms_in_port_t;
#else
typedef uint16_t ms_sa_family_t;
typedef uint32_t ms_socklen_t;
typedef uint32_t ms_in_addr_t;
typedef uint16_t ms_in_port_t;
#endif

#ifdef WITH_LWIP
typedef struct ip4_addr ms_ip4_addr_t;
typedef struct ip6_addr ms_ip6_addr_t;
#else
typedef struct ms_ip4_addr {
  uint32_t addr;
}ms_ip4_addr_t;

typedef struct ms_ip6_addr {
  uint32_t addr[4];
}ms_ip6_addr_t;
#endif

typedef struct
{
  unsigned long fds_bits[MS_FD_SETSIZE / 8 / sizeof(long)];
} ms_osal_socket_fd_set_t;

#ifdef WITH_LWIP
typedef struct sockaddr ms_sockaddr_t;
#else
typedef struct ms_sockaddr
{
  uint8_t sa_len;           /**<   Specifies the length of the structure.  */
  ms_sa_family_t sa_family; /**< Specifies the socket family.  */
  char sa_data[14];         /**< Specifies the address.  */
}ms_sockaddr_t;
#endif

struct ms_in6_addr
{
  union
  {
    uint8_t __u6_addr8[16];
    uint16_t __u6_addr16[8];
    uint32_t __u6_addr32[4];
  } __in6_u;
};

typedef struct ms_sockaddr_in6 {
    uint8_t            sin6_len;      /* length of this structure    */
    ms_sa_family_t     sin6_family;   /* AF_INET6                    */
    ms_in_port_t       sin6_port;     /* Transport layer port #      */
    uint32_t           sin6_flowinfo; /* IPv6 flow information       */
    struct ms_in6_addr sin6_addr;     /* IPv6 address                */
    uint32_t           sin6_scope_id; /* Set of interfaces for scope */
}ms_sockaddr_in6_t;

struct ms_sockaddr_storage {
  uint8_t        s2_len;
  ms_sa_family_t ss_family;
  char           s2_data1[2];
  uint32_t       s2_data2[3];
  uint32_t       s2_data3[3]; /* LWIP_IPV6 */
};

struct ms_in_addr
{
  ms_in_addr_t s_addr;
};

typedef struct ms_sockaddr_in
{
  uint8_t sin_len;                /***<   Specifies the socket length.  */
  ms_sa_family_t sin_family;      /***<   Specifies the socket family.  */
  ms_in_port_t sin_port;          /***<   Specifies the port.  */
  struct ms_in_addr sin_addr;     /***< Specifies the address.  */
  char sin_zero[MS_SIN_ZERO_LEN]; /***<  Indicates an 6-bit padding.  */
}ms_sockaddr_in_t;

#ifdef WITH_LWIP
typedef struct timeval ms_timeval_t;
#else
typedef struct ms_timeval
{
  long tv_sec;  /** seconds  */
  long tv_usec; /** and microseconds  */
}ms_timeval_t;
#endif

typedef struct ms_addrinfo
{
  int ai_flags;			/* Input flags.  */
  int ai_family;		/* Protocol family for socket.  */
  int ai_socktype;		/* Socket type.  */
  int ai_protocol;		/* Protocol for socket.  */
  ms_socklen_t ai_addrlen;		/* Length of socket address.  */
  ms_sockaddr_t *ai_addr;	/* Socket address for socket.  */
  char *ai_canonname;		/* Canonical name for service location.  */
  struct ms_addrinfo *ai_next;	/* Pointer to next in list.  */
}ms_addrinfo_t;

/**
 * @brief From the domain name to obtain IP address
 * @note get_ip_by_name() be similar to socket-gethostbyname
 * @param host_name: domain name address
 * @param dest_ip  : storage of the target IP address
 * @return if get the IP address successful return a nonnegative number, otherwise return a negative number
 */
int8_t ms_osal_socket_get_ip_by_name(const char *host_name, uint32_t *dest_ip);

/**
 * @brief create a nw socket
 * @note socket() be similar to socket-socket
 * @param domain     : protocol type
 * @param socket_type: socket type
 * @param protocol   : this value must be 0
 * @return if create successful return a positive number, otherwise return a negative number
 */
int ms_osal_socket_create(int domain, int socket_type, int protocol);

/**
 * @brief deleted a socket
 * @note socket() be similar to socket-close
 * @param sock_fd: the delete socket descriptor
 * @return if successful return 0, otherwise return a negative number
 */
int ms_osal_socket_close(int sock_fd);

/**
 * @brief set the socket attributes, eg: blocknon-blocking
 * @note setopt() be similar to socket-setsockopt
 * @param sock_fd: socket descriptor
 * @param level  : protocol
 * @param optname: Option name
 * @param optval : adress of the operand
 * @param optlen : the length of optval`
 */
int16_t ms_osal_socket_setopt(const int sock_fd, const int level, const int optname, const void *optval, ms_socklen_t optlen);

/**
 * @note getopt() be similar to socket-getsockopt
 */
int ms_osal_socket_getopt(int s, int level, int optname, void *optval, ms_socklen_t *optlen);

/**
 * @note getpeername() be similar to socket-getpeername
 */
int ms_osal_socket_getpeername(int s, ms_sockaddr_t *name, ms_socklen_t *namelen);

/**
 * @note getname() be similar to socket-getsockname
 */
int ms_osal_socket_getname(int s, ms_sockaddr_t *name, ms_socklen_t *namelen);

/**
 * @brief bind the socket to the specified port number
 * @param sock_fd: the socket descriptor will to be bind
 * @param my_addr: socket address
 * @param addrlen: socket address length
 * @return if successful return 0, otherwise return a negative number
 */
int ms_osal_socket_bind(int sock_fd, const ms_sockaddr_t *my_addr, ms_socklen_t addrlen);

/**
 * @brief send a connet request, need the target host port number and host ip when call this function
 * @note connect() be similar to socket-connect
 * @param sock_fd: the socket handle which want to connect
 * @param addr   : this address point to a struct include the host ip and port number 
 * @param addrlen: length of addr
 * @return if successful return 0, otherwise return a negative number
 */
int32_t ms_osal_socket_connect(int sock_fd, const ms_sockaddr_t *addr, uint32_t addrlen);

/**
 * @brief Listen to bind socket connection
 * @note listen() be similar to socket-listen
 * @param sock_fd: to listen this socket descriptor
 * @param backlog: Set the maximum length of the queue waiting for connecting 
 * @return if successful return 0, otherwise return a negative number
 */
int ms_osal_socket_listen(int sock_fd, int backlog);

/**
 * @brief Accept the client's connection request, if successful return a new socket descriptor
 * @note accept() be similar to socket-accept
 * @param sock_fd: The listening socket descriptor
 * @param addr   : point to a new connect target 
 * @param addrlen: length of addr
 * @return if successful return a positive number , otherwise return a negative number or 0
 */
int16_t ms_osal_socket_accept(int16_t sock_fd, ms_sockaddr_t *addr, ms_socklen_t *addrlen);

/**
 * @brief read data from socket
 * @param sock_fd  : get the data from this socket descriptor
 * @param buf      : store the recieve data
 * @param nbyte    : to read the data length 
 * @param wait_time: The blocking of data read time, unit is ms
 * @return 0: connect is closed
*             positive number: return the read data length
*             negative numbe: Network receiving errors
 */
int ms_osal_socket_recv(int sock_fd, void *buf, size_t nbyte, int flags);

/**
 * @brief send nbytes data to socket from buffer
 * @param sock_fd: Data will written to this socket descriptor
 * @param buf    : the data buffer which to send
 * @param nbyte  : the data length to send
 * @param flag   : please keep it to 0
 * @return if return a nonegative numbe, means the length of the write data successfully, otherwise send data fail
 */
int ms_osal_socket_send(int sock_fd, const void *buf, size_t nbyte, int flag);

/**
 * @brief send len bytes data to socket from buffer
 * @param sock_fd      : Data will written to this socket descriptor
 * @param buf          : the data buffer which to send
 * @param len          : the data length to send
 * @param flag         : please keep it to 0
 * @param target_addr  : this struct include the target host ip address
 * @param target_length: struct sockaddr length
 * @return if return a nonegative numbe, means the length of the write data successfully, otherwise send data fail
 */
int ms_osal_socket_sendto(int sock_fd, const void *buf, size_t len, int flag, const ms_sockaddr_t *target_addr, ms_socklen_t target_length);

/**
 * @brief read UDP data packet from specify IP address and port number
 * @param sock_fd      : The listening socket descriptor
 * @param buf          : the data will store in the buffer address
 * @param len          : the data length wish to read
 * @param flag         : keep it to 0
 * @param source_addr  : this struct include the source host ip address
 * @param source_length: struct sockaddr length
 * @return   positive number: The actual received data bytes
*            0: network connect error
*            negative numbe: Network receiving errors
 */
int ms_osal_socket_recvfrom(int sock_fd, void *buf, size_t len, int flags, ms_sockaddr_t *source_addr, ms_socklen_t *source_length);

/**
 * @note write() be similar to socket of c library
 */
int ms_osal_socket_write(int s, const void *data, size_t size);

/**
 * @brief Monitor descriptor file  of changes, if someone have read/write files will notified to the application layer
 * @note select() be similar to c library
 * @param maxfdp  : this value must be the number of descriptors plus one
 * @param readfds : Used to monitor the file descriptor read change
 * @param writefds: Used to monitor the file descriptor write change
 * @param errorfds: Used to monitor the file descriptor errors
 * @param timeout : 0--immediately  >0--use this value to timeout
 * @return positive number: get some file descriptors match the read/write attribute
 *         0: haven`t found some read/write attribute file descriptors , wait timeout
 *         negative numbe: occur some errors
 */
int ms_osal_socket_select(int maxfdp, ms_osal_socket_fd_set_t *readfds, ms_osal_socket_fd_set_t *writefds, ms_osal_socket_fd_set_t *errorfds, ms_timeval_t *timeout);

/**
 * @brief clean the file descriptor set
 * @note fd_zero() be similar to FD_ZERO
 * @param fd: Participate in reading and writing of the socket data collection
 * @return : null
 */
void ms_osal_socket_fd_zero(ms_osal_socket_fd_set_t *fd);

/**
 * @brief Will be set in the corresponding file descriptor fd position to 1
 * @note fd_set() be similar to FD_SET
 * @param fd : file descriptor
 * @param set: The file descriptor set
 * @return : null
 */
void ms_osal_socket_fd_set(uint16_t fd, ms_osal_socket_fd_set_t *set);

/**
 * @brief Will be set in the corresponding file descriptor fd position to 0
 * @note fd_clr() be similar to FD_CLR
 * @param fd : file descriptor
 * @param set: The file descriptor set
 * @return : null
 */
void ms_osal_socket_fd_clr(uint16_t fd, ms_osal_socket_fd_set_t *set);

/**
 * @brief to judge the fd  in the corresponding file descriptor is 1 or 0
 * @note fd_is_set() be similar to FD_ISSET
 * @param fd : file descriptor
 * @param set: The file descriptor set
 * @return : 1: this bit was set
 *           0: this bit is clean
 */
uint16_t ms_osal_socket_fd_is_set(uint16_t fd, ms_osal_socket_fd_set_t *set);

/**
 * @note htons() be similar to c library
 */
uint16_t ms_osal_socket_htons(uint16_t n);

/**
 * @note ntohs() be similar to c library
 */
uint16_t ms_osal_socket_ntohs(uint16_t n);

/**
 * @note ntohs() be similar to c library
 */
uint16_t ms_osal_socket_ntoa(uint16_t n);

/**
 * @note htonl() be similar to c library
 */
uint32_t ms_osal_socket_htonl(uint32_t n);

/**
 * @note ntohl() be similar to c library
 */
uint32_t ms_osal_socket_ntohl(uint32_t n);

/**
 * @note inet_ntop() be similar to c library
 */
const char *ms_osal_socket_inet_ntop(int af, const uint32_t *addr, char *buf, int buflen);

/**
 * @note inet_ntoa() be similar to c library
 */
char *ms_osal_socket_inet_ntoa(struct ms_in_addr in);

#ifdef LWIP_MSDK_IPV6_SUPPORT
/**
 * @note inet6_ntoa() be similar to c library
 */
char *ms_osal_socket_inet6_ntoa(struct ms_in6_addr in);
#endif

/**
 * @note inet_addr() be similar to c library
 */
ms_in_addr_t ms_osal_socket_inet_addr(const char *cp);

/**
 * @note inet_pton() be similar to c library
 */
int ms_osal_socket_inet_pton(int family, const char *strptr, void *addrptr);

/**
 * @note getaddrinfo() be similar to c library
 */
int ms_osal_socket_getaddrinfo(const char *nodename,               /* host 或者IP地址 */
                               const char *servname,               /* 十进制端口号 或者常用服务名称如"ftp"、"http"等 */
                               const ms_addrinfo_t *hints,    /* 获取信息要求设置 */
                               ms_addrinfo_t **restrict res); /* 获取信息结果 */

/**
 * @note freeaddrinfo() be similar to c library
 */
void ms_osal_socket_freeaddrinfo(ms_addrinfo_t *ai);

/**
 * @note getsockname() be similar to c library
 */
int ms_osal_socket_getsockname(int s, ms_sockaddr_t *name, ms_socklen_t *namelen);

/**
 * @note shutdown() be similar to c library
 */
int ms_osal_socket_shutdown(int s, int how);

/**
 * @note fcntl() be similar to c library
 */
int ms_osal_socket_fcntl(int s, int cmd, int val);

#ifdef MS_CONFIG_TCP_BUF_MONITOR_SUPPORT
/**
 * @note get all socket pbuf size ,for user to judge whether 
 * reconnect router to clear cache or not
 * @param 
 * @return size of all socket pbuf in tcp layer
 */
uint32_t ms_osal_socket_get_all_cache_size(void);
#endif

#ifdef __cplusplus
}
#endif

#endif // __MS_OSAL_SOCKET_H__
